// core/init_logrus.go
// =============================================================================
// | 包: core                                                                  |
// | 模块: 日志系统初始化                                                      |
// | 功能: 配置并初始化基于logrus的结构化日志系统                               |
// =============================================================================
package core

import (
	"blog_server/global"      // 全局变量包 - 存储系统核心配置和共享数据
	"bytes"                    // 字节缓冲区包 - 用于处理内存中的字节数据
	"fmt"                      // 格式化工具包 - 用于字符串格式化和输出
	"os"                       // 操作系统接口包 - 提供文件操作等系统功能
	"path"                     // 路径处理包 - 用于处理文件路径
	"time"                     // 时间处理包 - 提供时间相关功能

	"github.com/sirupsen/logrus" // 日志库 - 提供结构化日志记录功能
)

// =============================================================================
// | 常量定义                                                                  |
// =============================================================================

// 颜色常量 - 用于控制台日志颜色输出
// | 用途: 根据日志级别显示不同颜色，提升可读性                                  |
const (
	red    = 31 // 错误级别颜色 - 红色
	yellow = 33 // 警告级别颜色 - 黄色
	blue   = 36 // 信息级别颜色 - 蓝色
	gray   = 37 // 调试级别颜色 - 灰色
)

// =============================================================================
// | 结构体定义                                                                |
// =============================================================================

// LogFormatter - 自定义日志格式化器
// | 功能: 控制日志输出格式，支持颜色和结构化信息                               |
// | 用途: 实现logrus.Formatter接口，自定义日志输出格式                         |
type LogFormatter struct{}

// =============================================================================
// | 函数: Format                                                             |
// | 参数: @entry - *logrus.Entry - 日志条目                                   |
// | 返回: @[]byte - 格式化后的日志字节数组，@error - 错误信息                 |
// | 功能: 根据日志级别添加颜色，自定义时间格式、文件路径和函数名等信息           |
// | 说明: 实现logrus.Formatter接口，控制日志的输出样式和内容                   |
// |----------------------------------------------------------------------------|
// | 流程:                                                                     |
// | 1. 根据日志级别设置颜色代码 - 不同级别使用不同颜色增强可读性              |
// | 2. 准备字节缓冲区 - 使用传入的缓冲区或创建新的缓冲区                       |
// | 3. 格式化时间戳 - 自定义时间格式为"2006-01-02 15:04:05"                   |
// | 4. 检查是否包含调用者信息 - 如包含则添加文件路径和函数名                  |
// | 5. 格式化日志输出 - 组合所有信息生成最终的日志字符串                      |
// | 6. 返回格式化结果 - 将格式化后的字节数组和可能的错误返回                   |
// =============================================================================
func (t *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	//根据不同的level去展示颜色
	var levelColor int
	switch entry.Level {
	case logrus.DebugLevel, logrus.TraceLevel:
		levelColor = gray
	case logrus.WarnLevel:
		levelColor = yellow
	case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
		levelColor = red
	default:
		levelColor = blue
	}
	var b *bytes.Buffer
	if entry.Buffer != nil {
		b = entry.Buffer // 使用传入的缓冲区
	} else {
		b = &bytes.Buffer{} // 创建新的缓冲区
	}
	//自定义日期格式
	timestamp := entry.Time.Format("2006-01-02 15:04:05") // 格式化时间戳
	if entry.HasCaller() {
		//自定义文件路径
		funcVal := entry.Caller.Function // 获取调用函数名
		fileVal := fmt.Sprintf("%s:%d", path.Base(entry.Caller.File), entry.Caller.Line) // 获取调用文件和行号
		//自定义输出格式
		fmt.Fprintf(b, "[%s] \x1b[%dm[%s]\x1b[0m %s %s %s\n", timestamp, levelColor, entry.Level, fileVal, funcVal, entry.Message) // 格式化带调用者信息的日志
	} else {
		fmt.Fprintf(b, "[%s] \x1b[%dm[%s]\x1b[0m %s\n", timestamp, levelColor, entry.Level, entry.Message) // 格式化不带调用者信息的日志
	}
	return b.Bytes(), nil // 返回格式化后的日志字节数组
}

// FileDateHook - 按日期切分的文件日志钩子
// | 功能: 将日志写入按日期组织的文件中                                         |
// | 字段: file - 文件句柄，logPath - 日志目录，fileDate - 当前日期，appName - 应用名 |
type FileDateHook struct {
	file     *os.File // 文件句柄 - 用于写入日志
	logPath  string   // 日志目录 - 存储日志文件的目录路径
	fileDate string   // 当前日期 - 判断是否需要切换日志文件
	appName  string   // 应用名 - 日志文件名的一部分
}

// =============================================================================
// | 函数: Levels                                                             |
// | 返回: @[]logrus.Level - 钩子处理的日志级别列表                            |
// | 功能: 指定该钩子处理所有级别的日志                                         |
// | 说明: 实现logrus.Hook接口，定义钩子处理的日志级别范围                     |
// =============================================================================
func (hook FileDateHook) Levels() []logrus.Level {
	return logrus.AllLevels // 返回所有日志级别，表示处理所有类型的日志
}

// =============================================================================
// | 函数: Fire                                                               |
// | 参数: @entry - *logrus.Entry - 日志条目                                   |
// | 返回: @error - 错误信息                                                  |
// | 功能: 将日志写入文件，并在日期变化时创建新的日志文件                       |
// | 说明: 实现logrus.Hook接口，处理日志的写入操作和文件切换                   |
// |----------------------------------------------------------------------------|
// | 流程:                                                                     |
// | 1. 获取当前日志条目时间 - 格式化时间用于判断文件是否需要切换              |
// | 2. 转换日志条目为字符串 - 准备写入文件的内容                              |
// | 3. 检查日期是否变化 - 如果日期相同则直接写入当前文件                      |
// | 4. 日期变化处理 - 关闭当前文件，创建新的日期目录和日志文件               |
// | 5. 更新钩子状态 - 保存新的文件句柄和日期信息                             |
// | 6. 写入日志 - 将日志内容写入新的日志文件                                 |
// =============================================================================
func (hook FileDateHook) Fire(entry *logrus.Entry) error {
	timer := entry.Time.Format("2006-01-02_15") // 格式化日志时间
	line, _ := entry.String() // 转换日志条目为字符串
	if hook.fileDate == timer {
		hook.file.Write([]byte(line)) // 日期相同，直接写入当前文件
		return nil
	}
	// 时间不等，需要切换日志文件
	hook.file.Close() // 关闭当前文件
	os.MkdirAll(fmt.Sprintf("%s/%s", hook.logPath, timer), os.ModePerm) // 创建新的日期目录
	filename := fmt.Sprintf("%s/%s/%s.log", hook.logPath, timer, hook.appName) // 构建新的日志文件路径

	hook.file, _ = os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) // 打开新的日志文件
	hook.fileDate = timer // 更新当前日期
	hook.file.Write([]byte(line)) // 写入日志内容到新文件
	return nil
}

// =============================================================================
// | 函数: InitFile                                                           |
// | 参数: @logPath - string - 日志目录路径，@appName - string - 应用名称      |
// | 返回: 无                                                                  |
// | 功能: 创建日志目录，打开日志文件，并添加文件日志钩子                       |
// | 说明: 初始化文件日志系统，支持按日期组织日志文件                           |
// |----------------------------------------------------------------------------|
// | 流程:                                                                     |
// | 1. 获取当前日期 - 用于创建日期目录和日志文件                              |
// | 2. 创建日志目录 - 确保日志存储路径存在                                    |
// | 3. 处理目录创建错误 - 如失败则记录错误并返回                              |
// | 4. 构建日志文件路径 - 组合目录路径、日期和应用名                          |
// | 5. 打开日志文件 - 创建或追加到现有日志文件                                |
// | 6. 处理文件打开错误 - 如失败则记录错误并返回                              |
// | 7. 创建并注册文件日志钩子 - 将钩子添加到logrus全局日志器                  |
// =============================================================================
func InitFile(logPath, appName string) {
	fileDate := time.Now().Format("2006-01-02") // 获取当前日期
	//创建目录
	err := os.MkdirAll(fmt.Sprintf("%s/%s", logPath, fileDate), os.ModePerm) // 创建日期目录
	if err != nil {
		logrus.Error(err) // 记录目录创建错误
		return
	}

	filename := fmt.Sprintf("%s/%s/%s.log", logPath, fileDate, appName) // 构建日志文件路径
	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) // 打开日志文件
	if err != nil {
		logrus.Error(err) // 记录文件打开错误
		return
	}
	fileHook := FileDateHook{file, logPath, fileDate, appName} // 创建文件日志钩子
	logrus.AddHook(&fileHook) // 注册日志钩子
}

// =============================================================================
// | 函数: InitLogrus                                                         |
// | 参数: 无                                                                  |
// | 返回: 无                                                                  |
// | 功能: 配置全局日志器，设置输出、格式化器和日志级别，并初始化文件日志        |
// | 说明: 初始化完整的日志系统，包括控制台输出和文件记录                       |
// |----------------------------------------------------------------------------|
// | 流程:                                                                     |
// | 1. 设置日志输出 - 配置标准输出作为控制台日志输出                          |
// | 2. 开启调用者信息 - 记录日志调用的函数名和行号，便于调试                  |
// | 3. 设置自定义格式化器 - 应用自定义的LogFormatter格式化日志输出           |
// | 4. 设置日志级别 - 配置最低日志级别为Debug，捕获所有级别的日志            |
// | 5. 获取日志配置 - 从全局配置中读取日志相关设置                            |
// | 6. 初始化文件日志 - 调用InitFile函数配置文件日志系统                      |
// =============================================================================
func InitLogrus() {
	logrus.SetOutput(os.Stdout)          // 设置输出类型 - 标准输出
	logrus.SetReportCaller(true)         // 开启返回函数名和行号 - 便于调试
	logrus.SetFormatter(&LogFormatter{}) // 设置自己定义的Formatter - 自定义日志格式
	logrus.SetLevel(logrus.DebugLevel)   // 设置最低的Level - 调试级别

	l := global.Config.Log // 获取日志配置
	InitFile(l.Dir, l.App) // 初始化文件日志系统
}
